package com.lkd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.wxpay.plus.WxPayParam;
import com.github.wxpay.plus.WxPayTemplate;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.OrderContract;
import com.lkd.contract.VendoutContract;
import com.lkd.contract.VendoutData;
import com.lkd.contract.VendoutResultContract;
import com.lkd.dao.OrderDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.OrderEntity;
import com.lkd.exception.LogicException;
import com.lkd.feign.UserService;
import com.lkd.feign.VMService;
import com.lkd.service.OrderService;
import com.lkd.vo.PartnerVO;
import com.lkd.vo.PayReq;
import com.lkd.vo.SkuVO;
import com.lkd.vo.VmVO;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.client.RestHighLevelClient;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Map;

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    @Override
    public OrderEntity getByOrderNo(String orderNo) {
        QueryWrapper<OrderEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .eq(OrderEntity::getOrderNo,orderNo);
        return this.getOne(qw);
    }

    @Autowired
    private VMService vmService;

    @Autowired
    private UserService userService;

    @Autowired
    private MqttProducer mqttProducer;

    @Autowired
    private RedissonClient redissonClient;


    @Autowired
    private RestHighLevelClient esClient;

    @Autowired
    private WxPayTemplate wxPayTemplate;


    /*
    * 业务分析：
    *   0.判断是否开启事务：创建Order需要开启
    *   1.判断关键数据
    *       innerCode 和 skuId 不能为空
    *   2.判断业务数据
    *       售货机
    *           是否存在
    *           是否在运营
    *       商品
    *           判断是否存在
    *   3.业务操作
    *       3.1 创建订单
    *           订单编号：innerCode+现在时间的nanotime（只是在本项目并发量不大的情况可使用）
    *                   电商上面不适用上面的订单生成方式：项目标识+时间+随机字符串（16-32长度-一般不会重复）
    *
    *       3.2 调用第三方支付系统进行统一下单
    *
    *   4.返回结果（wxPay-plus返回的map数据）
    *   5.发送订单延时消息
    * */
    @Override
    @Transactional
    public Map<String, String> createOrderAndWxPayOrder(PayReq payReq) {

        //1.判断关键数据
        //  innerCode、openId、skuId
        if (StringUtils.isEmpty(payReq.getInnerCode())
                || StringUtils.isEmpty(payReq.getSkuId())
        ) {
            throw new LogicException("传入参数与接口不匹配");
        }

        // 通过 Redission 的数值原子性操作，对商品的预库存进行扣除
        var inventoryKey = VMSystem.getSkuInventoryKeyWithCode(payReq.getInnerCode(), payReq.getSkuId());

        // 扣减1并判断库存量
        RAtomicLong atomicLong = redissonClient.getAtomicLong(inventoryKey);
        long skuInventoryCount = atomicLong.decrementAndGet();
        if (skuInventoryCount < 0) {
            atomicLong.set(0);
            throw new LogicException("商品已经售卖完毕");
        }

        Map<String, String> resultMap = null;
        try {
            // 扣减售货机的预售库存



            // 2.判断业务数据
            //   售货机数据
            //      售货机是否存在
            //      售货机运营状态
            //      售货机是否有存货
            VmVO vm = vmService.getVMInfo(payReq.getInnerCode());
            if (ObjectUtils.isEmpty(vm)) {
                throw new LogicException("售货机不存在");
            }
            if (!(ObjectUtils.nullSafeEquals(VMSystem.VM_STATUS_RUNNING,vm.getVmStatus()))) {
                throw new LogicException("售货机运营状态异常");
            }



            //   商品数据
            //      判断商品是否存在
            SkuVO skuVo = vmService.getSku(payReq.getSkuId());
            if (ObjectUtils.isEmpty(skuVo)) {
                throw new LogicException("商品信息不存在");
            }


            // 3.创建订单数据
            //    售货机信息赋值
            //    商品信息赋值
            //    订单状态的赋值、合作商以及分成、支付状态、订单编号、openid


            OrderEntity orderEntity = new OrderEntity();

            //售货机数据编复制给订单对象
            BeanUtils.copyProperties( vm,orderEntity  );
            orderEntity.setAddr(vm.getNodeAddr());

            //商品复数据制给订单对象
            BeanUtils.copyProperties( skuVo,orderEntity  );
            // orderEntity.setAmount(skuVo.getRealPrice());//价格


            orderEntity.setStatus(VMSystem.ORDER_STATUS_CREATE);
            //计算合作商账单分账金额
            PartnerVO partner = userService.getPartner(vm.getOwnerId());
            BigDecimal bg = new BigDecimal(skuVo.getRealPrice());
            int bill = bg.multiply(new BigDecimal(partner.getRatio())).divide(new BigDecimal("100"),0, RoundingMode.HALF_UP).intValue();
            orderEntity.setBill(bill);


            // 支付状态、订单编号、openid
            orderEntity.setOpenId(payReq.getOpenId());//openId
            orderEntity.setPayStatus(VMSystem.PAY_STATUS_NOPAY);//支付状态
            orderEntity.setOrderNo(payReq.getInnerCode()+System.nanoTime());//订单编号

            this.save(orderEntity);

            // 4.调用第三方支付获得结果
            var wxPayParam=new WxPayParam();
            wxPayParam.setBody(orderEntity.getSkuName());//商品名称
            wxPayParam.setOutTradeNo(orderEntity.getOrderNo());//订单号
            wxPayParam.setTotalFee(orderEntity.getPrice().intValue());//金额
            wxPayParam.setOpenid(orderEntity.getOpenId());//用户id

            resultMap = wxPayTemplate.requestPay(wxPayParam);  //调用微信支付

            // 5.发送订单延时消息
            //将订单放到延迟队列中，5分钟后检查支付状态！！！！！！！！！！！！！！！！！！
            OrderContract orderContract = new OrderContract();
            orderContract.setOrderNo(orderEntity.getOrderNo());
            try {
                mqttProducer.send("$delayed/900/"+TopicConfig.ORDER_CHECK_TOPIC,2,orderContract);
            } catch (Exception e) {
                log.error("send to emq error",e);
                throw new LogicException("订单下单发送消息失败："+e.getLocalizedMessage());
            }

        } catch (LogicException e) {
            redissonClient.getAtomicLong(inventoryKey).incrementAndGet();
            throw e;
        } catch (BeansException e) {
            redissonClient.getAtomicLong(inventoryKey).incrementAndGet();
            throw e;
        }
        return resultMap;
    }



    @Override
    public boolean vendout(String orderNo,Long skuId,String innerCode) {
        //封装协议中的数据
        var reqData = new VendoutData();
        reqData.setOrderNo(orderNo);
        reqData.setSkuId(skuId);
        //封装协议
        var contract = new VendoutContract();
        contract.setVendoutData(reqData);
        contract.setInnerCode(innerCode);
        //向售货机微服务  发送出货请求
        try {
            mqttProducer.send( TopicConfig.VMS_VENDOUT_TOPIC,2,contract);
        } catch (JsonProcessingException e) {
            log.error("send vendout req error.",e);
            return false;
        }
        return true;
    }

    /*
    * 业务分析：
    *   1.解析和解析微信通知结果
    *      支付结果
    *   2.判断业务数据
    *       订单
    *           订单是否存在
    *           订单状态是否正常
    *           订单金额是否和支付平台金额一致
    *   3.修改订单的信息
    *       修改订单支付状态
    *       修改订单状态
    *   4.发送出货通知异步消息
    * */
    @Transactional
    public void processPaymentResult(Map<String, String> resultMap) {

        // 1.解析和解析微信通知结果
        //    支付结果
        var code = resultMap.get("code");
        if (!(WxPayTemplate.SUCCESS_FLAG.equals(code))) {
            throw new LogicException("微信通知为失败信息，不做业务操作: error-"+code);
        }



        // 2.判断业务数据
        //     订单
        //         订单是否存在
        //         订单状态是否正常
        //         订单金额是否和支付平台金额一致
        var order = this.getByOrderNo(resultMap.get("order_sn"));

        if (ObjectUtils.isEmpty(order)) {
            throw new LogicException("订单信息不存在");
        }
        if (!(ObjectUtils.nullSafeEquals(order.getStatus(),VMSystem.ORDER_STATUS_CREATE))) {
            throw new LogicException("订单状态异常");
        }

        if (!(resultMap.get("price").equals(order.getPrice().toString()))) {
            throw new LogicException("订单金额和支付平台金额不一致");
        }

        // 3.修改订单的信息
        var updateWrapper = new LambdaUpdateWrapper<OrderEntity>();
        updateWrapper.set(OrderEntity::getStatus, VMSystem.ORDER_STATUS_PAYED);
        updateWrapper.set(OrderEntity::getPayStatus, VMSystem.PAY_STATUS_PAYED);
        updateWrapper.set(OrderEntity::getUpdateTime, LocalDateTime.now());
        updateWrapper.set(OrderEntity::getThirdNo, resultMap.get("third_no"));
        updateWrapper.set(OrderEntity::getAmount, resultMap.get("price"));
        updateWrapper.eq(OrderEntity::getId, order.getId());

        if (!(this.update(updateWrapper))) {
            throw new LogicException("更改订单支付状态失败");
        }

        // 4.发送出货通知异步消息
        if (!(this.vendout(order.getOrderNo(),order.getSkuId(),order.getInnerCode()))) {
            throw new LogicException("发送出货通知异步消息失败");
        }
    }


    /*
    * 业务处理
    *   1.判断业务数据
    *       订单数据
    *           判断是否存在
    *           判断订单状态
    *   2.将订单改为失效状态
    * */
    @Override
    public void processTimeoutOrder(String orderNo) {

        // 1.判断业务数据
        //     订单数据
        //         判断是否存在
        //         判断订单状态
        //查询订单
        OrderEntity orderEntity = this.getByOrderNo(orderNo);
        if(orderEntity == null){
            log.warn("订单数据不存在 订单号：{}",orderNo);
            return;
        }

        if(orderEntity.getStatus().equals(VMSystem.ORDER_STATUS_CREATE)){  //如果
            log.error("订单状态异常 订单号：{}",orderNo);


            // 2.将订单改为失效状态
            var updateWrapper = new LambdaUpdateWrapper<OrderEntity>();
            updateWrapper.set(OrderEntity::getStatus, VMSystem.ORDER_STATUS_INVALID);
            updateWrapper.set(OrderEntity::getUpdateTime, LocalDateTime.now());
            updateWrapper.eq(OrderEntity::getId, orderEntity.getId());

            if (!(this.update(updateWrapper))) {
                log.error("超时订单修改状态失败，orderNo：{}",orderNo);
            }

            // 恢复redission商品的预库存数据
            String inventoryKey = VMSystem.getSkuInventoryKeyWithCode(orderEntity.getInnerCode(), orderEntity.getSkuId().toString());
            redissonClient.getAtomicLong(inventoryKey).incrementAndGet();


            return;
        }
        log.info("订单状态无超时，orderNo：{} , orderStatus: {}",orderNo,orderEntity.getStatus());
    }



    /*
     * 业务分析：
     *   0.是否开启事务
     *   1.判断关键数据
     *       系统内部数据，可以不用判断
     *   2.判断业务数据
     *       订单
     *           判断订单是否存在
     *           判断订单的状态
     *   3.业务操作
     *       判断售货机出货结果
     *       出货成功：修改订单的状态 -status：出货成功
     *       出货失败：调用第三方支付系统进行退款
     * */
    @Override
    @Transactional
    public boolean vendoutResult(VendoutResultContract vendoutResultContract) {

        // 0.是否开启事务
        // 1.判断关键数据
        //     系统内部数据，可以不用判断
        // 2.判断业务数据
        //     订单
        //         判断订单是否存在
        //         判断订单的状态  ORDER_STATUS_PAYED
        String orderNo = vendoutResultContract.getVendoutData().getOrderNo(); //取出订单号
        var queryWrapper = new LambdaQueryWrapper<OrderEntity>();
        queryWrapper.eq(OrderEntity::getOrderNo, orderNo);
        queryWrapper.eq(OrderEntity::getStatus,VMSystem.ORDER_STATUS_PAYED);

        if (this.count(queryWrapper) != 1) {
            log.error("订单或订单状态异常，orderNo：{}",orderNo);
            return false;
        }

        // 3.业务操作
        //     判断售货机出货结果
        //     出货成功：修改订单的状态 -status：出货成功
        //     出货失败：调用第三方支付系统进行退款
        //处理出货逻辑
        var updateWrapper = new LambdaUpdateWrapper<OrderEntity>();

        if( vendoutResultContract.isSuccess()){ //如果出货成功
            updateWrapper.set(OrderEntity::getStatus, VMSystem.ORDER_STATUS_VENDOUT_SUCCESS);
            log.info("出货成功  订单号:{}",orderNo);
        }else{   //出货失败
            updateWrapper.set(OrderEntity::getStatus, VMSystem.ORDER_STATUS_VENDOUT_FAIL);
            log.info("出货失败,发起退款 订单号:{}",orderNo);
            //todo: 发起退款


        }
        updateWrapper.set(OrderEntity::getUpdateTime, LocalDateTime.now());
        updateWrapper.eq(OrderEntity::getOrderNo, orderNo);

        return this.update(updateWrapper);
    }


}
